package com.yc.cinema;

import com.yc.cinema.dao.UserDao;
import com.yc.cinema.web.IndexAction;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyApplicationContext {

    private Map<String, BeanInfo> ioc = new HashMap<>();

    public Map<String, BeanInfo> getIoc() {
        return ioc;
    }

    public MyApplicationContext(Class configClass)
            throws InstantiationException, IllegalAccessException,
            InvocationTargetException, UnsupportedEncodingException, ClassNotFoundException {
        // 创建 IOC 容器 ==> id : bean 的 map集合

        // 创建配置类对象
        final Object configObject = configClass.newInstance();

        // 执行 bean 方法, 加载 bean 对象
        final Method[] declaredMethods = configClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            declaredMethod.setAccessible(true);
            // 只处理无参数方法, 带参数方法涉及 接口注入
            final Parameter[] parameters = declaredMethod.getParameters();
            if (parameters.length > 0) {
                continue;
            }
            final Object bean = declaredMethod.invoke(configObject);
            final Bean beanAnno = declaredMethod.getAnnotation(Bean.class);
            final String[] names = beanAnno.value();
            if (names != null && names.length > 0) {
                for (String name : names) {
                    BeanInfo beanInfo = new BeanInfo();
                    beanInfo.bean = bean;
                    beanInfo.isPrimary = declaredMethod.getAnnotation(Primary.class) != null;
                    beanInfo.isLazy = declaredMethod.getAnnotation(Lazy.class) != null;
                    final Scope scope = declaredMethod.getAnnotation(Scope.class);
                    if (scope != null) {
                        beanInfo.scope = scope.value();
                    }
                    ioc.put(name, beanInfo);
                }
            } else {
                //  没有指定 name 的情况
                // 获取当前的方法名
                BeanInfo beanInfo = new BeanInfo();
                beanInfo.bean = bean;
                beanInfo.isPrimary = declaredMethod.getAnnotation(Primary.class) != null;
                beanInfo.isLazy = declaredMethod.getAnnotation(Lazy.class) != null;
                final Scope scope = declaredMethod.getAnnotation(Scope.class);
                if (scope != null) {
                    beanInfo.scope = scope.value();
                }
                ioc.put(declaredMethod.getName(), beanInfo);
            }
        }
        // 扫描组件包 ComponentScan 类路径
        final ComponentScan conponentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        if (conponentScan != null) {
            List<Class> componentClassList = new ArrayList<>();
            for (String pkg : conponentScan.value()) {
                // pkg => com.yc.cinema
                final ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
                System.out.println(pkg);
                pkg = pkg.replaceAll("\\.", "/");
                final URL url = classLoader.getResource(pkg);
                final String path = URLDecoder.decode(url.getPath(), "utf-8");
                File dir = new File(path);
                System.out.println(dir.getAbsoluteFile());
                scanPackage(dir, componentClassList);
            }
            // 根据扫描的结果创建  bean
            for (Class aClass : componentClassList) {
                System.out.println("aClass = " + aClass);
                // 通过反射创建 bean
                final Object bean = aClass.newInstance();
                // 通过类上的组件注解获取 bean 的 name
                String name = aClass.getSimpleName(); // 类名
                // 默认名 类名 + 首字母小写
                name = name.substring(0, 1).toLowerCase() + name.substring(1);
                Component conponent = (Component) aClass.getAnnotation(Component.class);
                String value;
                if (conponent != null) {
                    value = conponent.value();
                    if (value != null && value.trim().isEmpty() == false) {
                        name = value;
                    }
                }

                Controller controller = (Controller) aClass.getAnnotation(Controller.class);
                if (controller != null) {
                    value = controller.value();
                    if (value != null && value.trim().isEmpty() == false) {
                        name = value;
                    }
                }

                Service service = (Service) aClass.getAnnotation(Service.class);
                if (service != null) {
                    value = service.value();
                    if (value != null && value.trim().isEmpty() == false) {
                        name = value;
                    }
                }

                Repository repository = (Repository) aClass.getAnnotation(Repository.class);
                if (repository != null) {
                    value = repository.value();
                    if (value != null && value.trim().isEmpty() == false) {
                        name = value;
                    }
                }

                // 加入 ioc 容器
                BeanInfo beanInfo = new BeanInfo();
                beanInfo.bean = bean;
                ioc.put(name, beanInfo);
            }
        }
        // 实现自动注入
        for (BeanInfo beanInfo : ioc.values()) {
            final Field[] declaredFields = beanInfo.bean.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                final Resource resource = field.getAnnotation(Resource.class);
                if (resource != null) {
                    // 默认基于 byName 注入
                    String name = resource.name();
                    if (name == null || name.trim().isEmpty()) {
                        //  如果 没有设置 name 属性, 那么使用 该属性名
                        name = field.getName();
                    }
                    // 获取bean
                    Object bean = null;
                    try {
                        bean = getBean(name);
                    } catch (RuntimeException e) {
                        // 根据name获取失败, 再根据 class 获取bean
                        bean = getBean(field.getType());
                        // 如果再获取不到 bean 那么就抛出异常了
                    }
                    // 将 bean 注入到 属性中
                    field.setAccessible(true);
                    field.set(beanInfo.bean, bean);
                } else {
                    final Autowired autowired = field.getAnnotation(Autowired.class);
                    if (autowired != null) {
                        // 默认基于 byType 注入
                        Object bean;
                        try {
                            // 先 使用 类型获取bean
                            bean = getBean(field.getType());
                        } catch (RuntimeException e) {
                            final Qualifier qualifier = field.getAnnotation(Qualifier.class);
                            String name;
                            if (qualifier == null) {
                                // 如果 bean 为空 null, 再根据限定词注解获取
                                name = field.getName();
                            } else {
                                // 如果没有 限定词 注解 , 再根据属性名字获取
                                name = qualifier.value();
                            }
                            bean = getBean(name);
                        }
                        field.setAccessible(true);
                        field.set(beanInfo.bean, bean);
                        // 相当于 beanInfo.bean.field = bean
                    }
                }
            }
        }
    }

    private void scanPackage(File dir, List<Class> componentClassList)
            throws UnsupportedEncodingException, ClassNotFoundException {
        for (File file : dir.listFiles()) {
            if (file.isFile()) {
//                System.out.println("==========" + file);
                // class文件 => 构建类路径 => 创建 bean => ioc 容器
                // 判断是否是服务组件, 如果是返回给组件类对象
                Class aClass = checkComponent(file);
                if (aClass != null) {
                    componentClassList.add(aClass);
                }
            } else if (file.isDirectory()) {
                System.out.println("递归扫描: " + file);
                // 目录(包) => 递归调用
                scanPackage(file, componentClassList);
            } else {
                throw new RuntimeException("该文件不存在:" + file);
                // 抽象的文件对象, 磁盘上没有对应的文件对象
//                final File file1 = new File("abcasdfasdf");
//                file1.createNewFile(); // 文件
//                file1.mkdir();   // 目录
            }
        }
    }

    private Class checkComponent(File classFile) throws UnsupportedEncodingException, ClassNotFoundException {
        final URL url = getClass().getClassLoader().getResource("");
        final String path = URLDecoder.decode(url.getPath(), "utf-8");
        File classes = new File(path);
        final String absoluteFile = classFile.getAbsolutePath();
        final String absoluteFile1 = classes.getAbsolutePath();
        String classPath = absoluteFile.replace(absoluteFile1 + "\\", "");
        classPath = classPath.replace(".class", "");
        classPath = classPath.replaceAll("\\\\", ".");
        System.out.println("classPath = " + classPath);
        Class cls = Class.forName(classPath);
        // 判断 4 个组件注解
        Annotation annotation = cls.getAnnotation(Component.class);
        if (annotation != null) return cls;
        annotation = cls.getAnnotation(Controller.class);
        if (annotation != null) return cls;
        annotation = cls.getAnnotation(Service.class);
        if (annotation != null) return cls;
        annotation = cls.getAnnotation(Repository.class);
        if (annotation != null) return cls;
        return null;
    }

    public static void main(String[] args) throws Exception {
        final MyApplicationContext ctx = new MyApplicationContext(AnnoConfig.class);
        final IndexAction indexAction = ctx.getBean(IndexAction.class);
        System.out.println(indexAction);
        System.out.println("-------------------------");
        System.out.println(indexAction.getUbiz());
        System.out.println(indexAction.getMbiz());
        System.out.println("-------------------------");
        final UserDao userDao = ctx.getBean(UserDao.class);
        System.out.println(userDao);
    }

    public Object getBean(String name) {
        BeanInfo beanInfo = ioc.get(name);
        if (beanInfo == null) {
            throw new RuntimeException("没有该bean: " + name);
        }
        return beanInfo.bean;
    }

    public <T> T getBean(Class<T> cls) {
        // 根据类型查找所有该类型的bean
        List<BeanInfo> beanInfoList = new ArrayList<>();
        for (BeanInfo beanInfo : ioc.values()) {
            if (cls.isAssignableFrom(beanInfo.bean.getClass())) {
                beanInfoList.add(beanInfo);
            }
        }
        // 如果结果为 0 => 报错
        if (beanInfoList.isEmpty()) {
            throw new RuntimeException("没有该bean: " + cls);
        }
        // 如果结果为 1 => 返回
        else if (beanInfoList.size() == 1) {
            return (T) beanInfoList.get(0).bean;
        } else {
            // 如果结果 > 1 => 判断有没有 主 bean
            BeanInfo primaryBeanInfo = null;
            for (BeanInfo beanInfo : beanInfoList) {
                if (beanInfo.isPrimary) {
                    primaryBeanInfo = beanInfo;
                    break;
                }
            }
            //                  => 没有则报错
            if (primaryBeanInfo == null) {
                throw new RuntimeException("有多个该类型的bean!");
            }
            //                  => 有 则返回 主 bean
            return (T) primaryBeanInfo.bean;
        }
    }

    @Data
    protected class BeanInfo {
        Object bean;
        boolean isPrimary;
        boolean isLazy;
        String scope;
    }

}
